using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;
using DirkRettschlag.Utils;

namespace Departure
{

    /// <summary>
    /// A Kinetic list control.
    /// </summary>
    public class KListControl : UserControl
    {
        #region Interface
        /// <summary>
        /// Interface for items contained within the list.
        /// </summary>
        public interface IKListItem
        {
            /// <summary>
            /// Gets or sets the parent.
            /// </summary>
            /// <value>The parent.</value>
            KListControl Parent { get; set; }

            /// <summary>
            /// The unscrolled bounds for this item.
            /// </summary>
            Rectangle Bounds { get; set; }

            /// <summary>
            /// Gets or sets the X.
            /// </summary>
            /// <value>The X.</value>
            int XIndex { get; set; }

            /// <summary>
            /// Gets or sets the Y.
            /// </summary>
            /// <value>The Y.</value>
            int YIndex { get; set; }

            /// <summary>
            /// Gets or sets a value indicating whether this <see cref="IKListItem"/> is selected.
            /// </summary>
            /// <value><c>true</c> if selected; otherwise, <c>false</c>.</value>
            bool Selected { get; set; }

            /// <summary>
            /// Gets or sets the text.
            /// </summary>
            /// <value>The text.</value>
            string Text { get; set; }

            /// <summary>
            /// Gets or sets the value.
            /// </summary>
            /// <value>The value.</value>
            object Value { get; set; }

            /// <summary>
            /// Renders the specified graphics object.
            /// </summary>
            /// <param name="g">The graphics.</param>
            /// <param name="bounds">The bounds.</param>
            void Render(Graphics g, Rectangle bounds);

            void Dispose();

            string FastAccessValue { get; set; }

            string Key { get; set; }
        }

        #endregion Interface

        /// <summary>
        /// Initializes a new instance of the <see cref="KListControl"/> class.
        /// </summary>
        public KListControl()
        {
            CreateBackBuffer();

            m_timer.Interval = 10;
            m_timer.Tick += new EventHandler(m_timer_Tick);

            m_stringFormat.Alignment = StringAlignment.Center;
            m_stringFormat.LineAlignment = StringAlignment.Center;
        }

        /// <summary>
        /// Releases the unmanaged resources used by the <see cref="T:System.Windows.Forms.Control"></see> and its child controls and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            CleanupBackBuffer();

            m_timer.Enabled = false;

            base.Dispose(disposing);
        }

        //public delegate void ItemDoubleClickedDelegate(IKListItem item);
        //public ItemDoubleClickedDelegate OnItemDoubleClicked;

        public event EventHandler OnItemDoubleClicked;

        /// <summary>
        /// Occurs when the selected item changes.
        /// </summary>
        public event EventHandler SelectedItemChanged;

        /// <summary>
        /// Occurs when the selected item is clicked on (after already being selected).
        /// </summary>
        public event EventHandler SelectedItemClicked;

        /// <summary>
        /// Gets the <see cref="Scroller.KListControl.IKListItem"/> at the specified index.
        /// </summary>
        public IKListItem this[int index]
        {
            get
            {
                return m_items[0][index];
            }
        }

        /// <summary>
        /// Gets the <see cref="Scroller.KListControl.IKListItem"/> at the specified index.
        /// </summary>
        public IKListItem this[int x, int y]
        {
            get
            {
                return m_items[x][y];
            }
        }

        /// <summary>
        /// The selected index.
        /// </summary>
        public Point SelectedIndex
        {
            get
            {
                return m_selectedIndex;
            }
        }

        /// <summary>
        /// Gets the selected item.
        /// </summary>
        /// <value>The selected item.</value>
        public IKListItem SelectedItem
        {
            get
            {
                return m_selectedItem;
            }
            set
            {
                if (m_selectedItem != null)
                    m_selectedItem.Selected = false;
                m_selectedItem = value;
                m_selectedItem.Selected = true;
            }
        }

        /// <summary>
        /// Gets the item count.
        /// </summary>
        /// <value>The count.</value>
        public int Count()
        {
            return m_items.Count;
        }

        public int Count(int Column)
        {
            if (m_items.ContainsKey(Column))
                return m_items[Column].Count;
            else
                return 0;
        }

        /// <summary>
        /// Gets or sets the maximum scroll velocity.
        /// </summary>
        /// <value>The maximum velocity.</value>
        public int MaxVelocity
        {
            get
            {
                return m_maxVelocity;
            }
            set
            {
                m_maxVelocity = value;
            }
        }

        /// <summary>
        /// Gets or sets the height of items in the control.
        /// </summary>
        /// <value>The height of the items.</value>
        public int ItemHeight
        {
            get
            {
                // In horizontal mode, we just use the full bounds, other modes use m_itemHeight.
                return m_layout == KListLayout.Horizontal ? ClientRectangle.Height : m_itemHeight;
            }
            set
            {
                m_itemHeight = value;
                Reset();
            }
        }

        /// <summary>
        /// Gets or sets the height of items in the control.
        /// </summary>
        /// <value>The height of the items.</value>
        public int ItemWidth
        {
            get
            {
                // In vertical mode, we just use the full bounds, other modes use m_itemWidth.
                return m_layout == KListLayout.Vertical ? ClientRectangle.Width : m_itemWidth;
            }
            set
            {
                m_itemWidth = value;
                Reset();
            }
        }

        /// <summary>
        /// Gets or sets the width of the fast access bar.
        /// </summary>
        /// <value>The width of the bar.</value>
        public int FastAccessWidth
        {
            get
            {
                return m_fastAccessWidth;
            }
            set
            {
                m_fastAccessWidth = value;
                Reset();
            }
        }

        /// <summary>
        /// Gets or sets the layout.
        /// </summary>
        /// <value>The layout.</value>
        public KListLayout Layout
        {
            get
            {
                return m_layout;
            }
            set
            {
                m_layout = value;
            }
        }

        public SkinSettings SkinSettings
        {
            get
            {
                return this.skinSettings;
            }
            set
            {
                this.skinSettings = value;
                this.ForeColor = skinSettings.FontColor;
                this.drawStringBrush = new SolidBrush(this.skinSettings.FontColor);
                rectangleImageBG = new Rectangle(0, 0, skinSettings.BackGround.Width, skinSettings.BackGround.Height);
                Invalidate();
            }
        }

        public bool VGA
        {
            get
            {
                return this.vga;
            }
            set
            {
                this.vga = value;
            }
        }
                
        public bool WideFastAccess
        {
            get
            {
                return this.wideFastAccess;
            }
            set
            {
                this.wideFastAccess = value;

            }
        }

        public SortedList<string, Bitmap> TrainIcons
        {
            get 
            { 
                return this.trainIcons; 
            }
            set
            {
                this.trainIcons = value;

            }
        }

        public bool IsFocused
        {
            get { return this.isFocused; }
            set { this.isFocused = value; }
        }

        public IKListItem GetItemByKey(string key)
        {
            for (int i = 0; i < this.Count(0); i++)
            {
                if (this[i].Key == key)
                    return this[i];
            }

            return null;
        }

        /// <summary>
        /// Adds an item.
        /// </summary>
        /// <param name="item">The item.</param>
        public void AddItem(IKListItem item)
        {
            item.Parent = this;
            item.Selected = false;
            if (!m_items.ContainsKey(item.XIndex))
            {
                m_items.Add(item.XIndex, new ItemList());
            }
            item.Bounds = ItemBounds(item.XIndex, item.YIndex);
            m_items[item.XIndex].Add(item.YIndex, item);
           
            Reset();
        }

        delegate void RemoveItemCallback(IKListItem item);
        /// <summary>
        /// Removes an item.
        /// </summary>
        /// <param name="item">The item.</param>
        public void RemoveItem(IKListItem item)
        {
            if (this.InvokeRequired)
            {
                RemoveItemCallback d = new RemoveItemCallback(RemoveItem);
                this.Invoke(d, new object[] { item});
            }
            else
            {
                if (m_items.ContainsKey(item.XIndex) && m_items[item.XIndex].ContainsKey(item.YIndex))
                {
                    ItemList tempList = new ItemList();

                    for (int i = 0; i < m_items[item.XIndex].Count; i++)
                    {
                        if (i != item.YIndex)
                        {
                            m_items[item.XIndex][i].Bounds = ItemBounds(item.XIndex, tempList.Count);
                            m_items[item.XIndex][i].XIndex = item.XIndex;
                            m_items[item.XIndex][i].YIndex = tempList.Count;
                            tempList.Add(tempList.Count, m_items[item.XIndex][i]);
                        }
                    }

                    m_items[item.XIndex] = tempList;

                    if (item == m_selectedItem)
                    {
                        m_selectedItem.Selected = false;
                        m_selectedItem = null;
                        m_selectedIndex = new Point(-1, -1);
                    }
#if DEBUG
                    if (item.GetType() == typeof(ctrlStationItem))
                        Debug.WriteLine("Removing: " + ((ctrlStationItem)item).Time + " - " + ((ctrlStationItem)item).Train + " - " + ((ctrlStationItem)item).Direction);
#endif
                    item.Dispose();
                }
                Reset();
            }
        }

        public void SortItems()
        {
            ItemList tempList = new ItemList();

            SortedList<string, IKListItem> sort = new SortedList<string, IKListItem>(); 

            for (int i = 0; i < m_items[0].Count; i++)
            {
                sort.Add(m_items[0][i].Text + m_items[0][i].Key, m_items[0][i]);
            }

            foreach (IKListItem item in sort.Values)
            {
                item.Bounds = ItemBounds(item.XIndex, tempList.Count);
                item.XIndex = item.XIndex;
                item.YIndex = tempList.Count;
                tempList.Add(tempList.Count, item);
            }

            m_items[0] = tempList;
        }

        public void OrderItems(List<string> order)
        {
            ItemList tempList = new ItemList();

            foreach (string key in order)
            {
                foreach (IKListItem item in m_items[0].Values)
                {
                    if (item.Key == key)
                    {
                        item.Bounds = ItemBounds(item.XIndex, tempList.Count);
                        item.XIndex = item.XIndex;
                        item.YIndex = tempList.Count;
                        tempList.Add(tempList.Count, item);
                    }
                }

            }

            m_items[0] = tempList;
        }

        /// <summary>
        /// Clears the list.
        /// </summary>
        public void Clear()
        {
            m_items.Clear();
            Reset();
        }

        /// <summary>
        /// Invalidates the item (when visible).
        /// </summary>
        /// <param name="item">The item.</param>
        public void Invalidate(IKListItem item)
        {
            Rectangle itemBounds = item.Bounds;
            itemBounds.Offset(-m_offset.X, -m_offset.Y);
            if (Bounds.IntersectsWith(itemBounds))
            {
                Invalidate(itemBounds);
            }
        }

        /// <summary>
        /// Begins updates - suspending layout recalculation.
        /// </summary>
        public void BeginUpdate()
        {
            m_updating = true;
        }

        /// <summary>
        /// Ends updates - re-enabling layout recalculation.
        /// </summary>
        public void EndUpdate()
        {
            m_updating = false;
            Reset();
        }

        /// <summary>
        /// Called when the control is resized.
        /// </summary>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            if (skinSettings != null)
                rectangleImageBG = new Rectangle(0, 0, skinSettings.BackGround.Width, skinSettings.BackGround.Height);
            CreateBackBuffer();
            Reset();
        }

        /// <summary>
        /// Handles the Tick event of the m_timer control.
        /// </summary>
        private void m_timer_Tick(object sender, EventArgs e)
        {
            if (!Capture && (m_velocity.Y != 0 || m_velocity.X != 0))
            {
                m_offset.Offset(m_velocity.X, m_velocity.Y);

                ClipScrollPosition();

                // Slow down
                if (((++m_timerCount) % 8) == 0)
                {
                    if (m_velocity.Y < 0)
                    {
                        m_velocity.Y++;
                    }
                    else if (m_velocity.Y > 0)
                    {
                        m_velocity.Y--;
                    }
                    if (m_velocity.X < 0)
                    {
                        m_velocity.X++;
                    }
                    else if (m_velocity.X > 0)
                    {
                        m_velocity.X--;
                    }
                }

                if (m_velocity.Y == 0 && m_velocity.X == 0)
                {
                    m_timer.Enabled = false;
                }

                Invalidate();
            }
        }

        private void drawFastAccess(Graphics g, Rectangle bounds)
        {
            SizeF size;
            if (wideFastAccess)
            {
                g.DrawImage(skinSettings.FastAccessWide, new Rectangle((bounds.Width - skinSettings.FastAccessWide.Width) / 2, (bounds.Height - skinSettings.FastAccess.Height) / 2, skinSettings.FastAccessWide.Width, skinSettings.FastAccessWide.Height), new Rectangle(0, 0, skinSettings.FastAccessWide.Width, skinSettings.FastAccessWide.Height), GraphicsUnit.Pixel);
                using (Font f2 = AppropriateFont(g, 5, 14, new Size(skinSettings.FastAccessWide.Width, skinSettings.FastAccessWide.Height), getFastAccessString(), this.Font, out size))
                {
                    g.DrawString(getFastAccessString(), f2, new SolidBrush(skinSettings.FontColor), (bounds.Width - size.Width) / 2, (bounds.Height - size.Height) / 2);
                }
            }
            else
            {
                g.DrawImage(skinSettings.FastAccess, new Rectangle((bounds.Width - skinSettings.FastAccess.Width) / 2, (bounds.Height - skinSettings.FastAccess.Height) / 2, skinSettings.FastAccess.Width, skinSettings.FastAccess.Height), new Rectangle(0, 0, skinSettings.FastAccess.Width, skinSettings.FastAccess.Height), GraphicsUnit.Pixel);
                using (Font f2 = AppropriateFont(g, 5, 200, new Size(skinSettings.FastAccess.Width, skinSettings.FastAccess.Height), getFastAccessString(), this.Font, out size))
                {
                    g.DrawString(getFastAccessString(), f2, new SolidBrush(skinSettings.FontColor), (bounds.Width - size.Width) / 2, (bounds.Height - size.Height) / 2);
                }
            }
            
        }

        public static Font AppropriateFont(Graphics g, float minFontSize, float maxFontSize, Size layoutSize, string s, Font f, out SizeF extent)
        {
            if (maxFontSize == minFontSize)
                f = new Font(f.Name, minFontSize, f.Style);

            extent = g.MeasureString(s, f);

            if (maxFontSize <= minFontSize)
                return f;

            float hRatio = layoutSize.Height / extent.Height;
            float wRatio = layoutSize.Width / extent.Width;
            float ratio = (hRatio < wRatio) ? hRatio : wRatio;

            float newSize = f.Size * ratio;

            if (newSize < minFontSize)
                newSize = minFontSize;
            else if (newSize > maxFontSize)
                newSize = maxFontSize;

            f = new Font(f.Name, newSize, f.Style);
            extent = g.MeasureString(s, f);

            return f;
        }

        private string getFastAccessString()
        {
            decimal fastAccessItemHeight = Convert.ToDecimal(this.ClientRectangle.Height) / m_items[0].Count;
            fastAccessItem = Convert.ToInt32(Math.Round(Convert.ToDecimal(m_mouseFastAccess.Y) / fastAccessItemHeight,0));
            if (m_items[0].ContainsKey(fastAccessItem))
            {
                fastAccess = m_items[0][fastAccessItem].FastAccessValue;
            }

            return fastAccess;
        }

        /// <summary>
        /// Paints the background of the control.
        /// </summary>
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            // Do nothing
        }

        /// <summary>
        /// Paints the control.
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            if (m_backBuffer != null)
            {
#if DEBUG
                Stopwatch stopWatch = Stopwatch.StartNew();
#endif
                if (fastAccessRect.Contains(m_mouseDown))
                {
                    if (m_items.Count > 0)
                    {
                        if (m_items[0].Count > 0)
                        {
                            drawFastAccess(m_backBuffer, ClientRectangle);
                        }
                    }
                }
                else
                {
                    if (m_items.Count > 0)
                    {
                        if (m_items[0].Count * ItemHeight < this.Height) // only draw if needed
                        {
                            m_backBuffer.DrawImage(skinSettings.BackGround, this.ClientRectangle, rectangleImageBG, GraphicsUnit.Pixel);
                        }
                        else if (m_items[0].Count == 0)
                        {
                            m_backBuffer.DrawImage(skinSettings.BackGround, this.ClientRectangle, rectangleImageBG, GraphicsUnit.Pixel);
                        }
                    }
                    else
                    {
                        m_backBuffer.DrawImage(skinSettings.BackGround, this.ClientRectangle, rectangleImageBG, GraphicsUnit.Pixel);
                    }

                    startIndex = FindIndex(ClientRectangle.Left, ClientRectangle.Top);

                    int x = 0;
                    int y = 0;
                    moreX = false;

                    if (x < m_items.Count)
                    {
                        x = startIndex.X;
                        moreX = true;
                    }

                    while (moreX)
                    {
                        yList = m_items[x];
                        if (yList != null)
                        {
                            moreY = false;

                            if (y < yList.Count)
                            {
                                y = startIndex.Y;
                                moreY = true;
                            }

                            while (moreY)
                            {
                                currentItem = yList[y];
                                if (currentItem != null)
                                {
                                    itemRect = new Rectangle(currentItem.Bounds.X, currentItem.Bounds.Y, this.ItemWidth, currentItem.Bounds.Height);
                                    itemRect.Offset(-m_offset.X, -m_offset.Y);
                                    if (ClientRectangle.IntersectsWith(itemRect))
                                    {
                                        currentItem.Render(m_backBuffer, itemRect);
                                    }
                                    else
                                    {
                                        if (y != startIndex.Y)
                                            break;
                                    }
                                }

                                if (y + 1 < yList.Count)
                                {
                                    y = y + 1;
                                    moreY = true;
                                }
                                else
                                {
                                    moreY = false;
                                }
                            }
                        }

                        if (x + 1 < m_items.Count)
                        {
                            x = x + 1;
                            moreX = true;
                        }
                        else
                        {
                            moreX = false;
                        }
                    }
                }

                e.Graphics.DrawImage(m_backBufferBitmap, 0, 0);
#if DEBUG
                stopWatch.Stop();

                Debug.WriteLine("KList Render Time:   " + stopWatch.ElapsedMilliseconds.ToString());
#endif
            }
            else
            {
                base.OnPaint(e);
            }
        }

        public void HookKeyDown(Control control)
        {
            control.KeyDown += new KeyEventHandler(OnKeyDown);
        }
              

        protected void OnKeyDown(object sender, KeyEventArgs e)
        {
            if (isFocused)
            {
                if (e.KeyCode == System.Windows.Forms.Keys.Up)
                {
                    try
                    {
                        if (m_selectedIndex.Y > 0)
                        {
                            m_selectedIndex.Y--;
                            SelectAndJump();
                        }
                        e.Handled = true;
#if DEBUG
                        Debug.WriteLine("Selected Index:   " + m_selectedIndex.Y.ToString());
#endif
                    }
                    catch
                    {
                    }
                    this.Invalidate();
                }
                if (e.KeyCode == System.Windows.Forms.Keys.Down)
                {
                    try
                    {
                        if (m_selectedIndex.Y < m_items[0].Count - 1)
                        {
                            m_selectedIndex.Y++;
                            SelectAndJump();
                        }
                        e.Handled = true;
#if DEBUG
                        Debug.WriteLine("Selected Index:   " + m_selectedIndex.Y.ToString());
#endif
                    }
                    catch
                    {
                    }
                    this.Invalidate();
                }
                if (e.KeyCode == System.Windows.Forms.Keys.Enter)
                {
                    try
                    {
                        if (e.Handled == false)
                        {
                            if (OnItemDoubleClicked != null)
                            {
                                OnItemDoubleClicked(this.Parent, EventArgs.Empty);
                            }
                            e.Handled = true;
                        }
                    }
                    catch
                    {
                    }
                    this.Invalidate();
                }
            }
        }

        private void SelectAndJump()
        {
            try
            {
                IKListItem item = null;
                try
                {
                    item = m_items[0][m_selectedIndex.Y];
                }
                catch (KeyNotFoundException)
                {
                    return;
                }

                if (m_selectedItem != null)
                {
                    m_selectedItem.Selected = false;
                }
                m_selectedItem = item;
                m_selectedItem.Selected = true;

                if (SelectedItemChanged != null)
                {
                    SelectedItemChanged(this, new EventArgs());
                }

            }
            catch (KeyNotFoundException)
            {
                return;
            }
            if (SelectedItemChanged != null)
            {
                SelectedItemChanged(this, new EventArgs());
            }

            JumpToItem(m_selectedItem);
            Invalidate();
        }

        public void JumpToItem(IKListItem item)
        {
            Rectangle VisibleBounds = new Rectangle(0, m_offset.Y, 10, this.Height);
            Rectangle CheckAgainstBounds = new Rectangle(0, item.Bounds.Top, 10, item.Bounds.Height);
            while (!VisibleBounds.Contains(CheckAgainstBounds))
            {
                if (item.Bounds.Top >= VisibleBounds.Top)
                {
                    m_offset.Y = m_offset.Y + this.ItemHeight;
                }
                else
                {
                    m_offset.Y = m_offset.Y - this.ItemHeight;
                }

                if (m_offset.Y < 0)
                {
                    m_offset.Y = 0;
                    break;
                }
                if (m_offset.Y > (m_items[0].Count - 1) * this.ItemHeight)
                {
                    m_offset.Y = m_items[0].Count * this.ItemHeight;
                    break;
                }

                VisibleBounds = new Rectangle(0, m_offset.Y, 10, this.Height);

            }
            Invalidate();
        }

        /// <summary>
        /// Called when the user clicks on the control with the mouse.
        /// </summary>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            Capture = true;

            m_mouseDown.X = e.X;
            m_mouseDown.Y = e.Y;
            m_mousePrev = m_mouseDown;

            if (fastAccessRect.Contains(m_mouseDown))
            {
                m_mouseFastAccess.X = e.X;
                m_mouseFastAccess.Y = e.Y;
                Invalidate();
            }

        }

        /// <summary>
        /// Called when the user moves the mouse over the control.
        /// </summary>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (e.Button == MouseButtons.Left)
            {
                if (fastAccessRect.Contains(m_mouseDown))
                {
                    m_mouseFastAccess.X = e.X;
                    m_mouseFastAccess.Y = e.Y;
                }
                else
                {
                    Point currPos = new Point(e.X, e.Y);

                    int distanceX = m_mousePrev.X - currPos.X;
                    int distanceY = m_mousePrev.Y - currPos.Y;

                    m_velocity.X = distanceX / 2;
                    m_velocity.Y = distanceY / 2;
                    
                    ClipVelocity();

                    m_offset.Offset(distanceX, distanceY);
                    
                    ClipScrollPosition();

                    m_mousePrev = currPos;
                }
                Invalidate();
            }
        }

        /// <summary>
        /// Called when the user releases a mouse button.
        /// </summary>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            if (fastAccessRect.Contains(m_mouseDown))
            {
                m_mouseFastAccess.X = e.X;
                m_mouseFastAccess.Y = e.Y;
                m_offset.Y = fastAccessItem * m_itemHeight;
                ClipScrollPosition();
                m_velocity.Y = 0;
                m_timer.Enabled = true;
            }
            else
            {
                // Did the click end on the same item it started on?
                bool sameX = Math.Abs(e.X - m_mouseDown.X) < m_itemWidth;
                bool sameY = Math.Abs(e.Y - m_mouseDown.Y) < m_itemHeight;

                if ((m_layout == KListLayout.Vertical && sameY) ||
                    (m_layout == KListLayout.Horizontal && sameX) ||
                    (m_layout == KListLayout.Grid && sameX && sameY))
                {                    
                    // Yes, so select that item.
                    Point selectedIndex = FindIndex(e.X, e.Y);

#if DEBUG
                    Debug.WriteLine("Selected Index:   " + selectedIndex.Y.ToString());
#endif

                    if (selectedIndex != m_selectedIndex)
                    {
                        IKListItem item = null;
                        if (m_items.ContainsKey(selectedIndex.X) && m_items[selectedIndex.X].TryGetValue(selectedIndex.Y, out item))
                        {
                            if (m_selectedItem != null)
                            {
                                m_selectedItem.Selected = false;
                            }
                            m_selectedIndex = selectedIndex;
                            m_selectedItem = item;
                            m_selectedItem.Selected = true;

                            if (SelectedItemChanged != null)
                            {
                                SelectedItemChanged(this, new EventArgs());
                            }
                        }
                    }
                    else
                    {
                        if (SelectedItemClicked != null)
                        {
                            SelectedItemClicked(this, new EventArgs());
                        }
                    }

                    m_velocity.X = 0;
                    m_velocity.Y = 0;
                }
                else
                {
                    m_timer.Enabled = true;
                }
            }
            m_mouseDown.Y = -1;
            Capture = false;
            
            Invalidate();
        }

        /// <summary>
        /// Resets the drawing of the list.
        /// </summary>
        private void Reset()
        {
            if (!m_updating)
            {
                m_timer.Enabled = false;
                //if (m_selectedItem != null)
                //{
                //    m_selectedItem.Selected = false;
                //    m_selectedItem = null;
                //}
                //m_selectedIndex = new Point(-1, -1);
                //Capture = false;
                //m_velocity.X = 0;
                //m_velocity.Y = 0;
                //m_offset.X = 0;
                m_offset.Y -= m_itemHeight;

                if (m_offset.Y < 0) m_offset.Y = 0;

                fastAccessRect = new Rectangle(this.ClientRectangle.Width - m_fastAccessWidth, 0, m_fastAccessWidth, this.ClientRectangle.Height);

                //if (m_selectedItem != null)
                //{
                //    JumpToItem(m_selectedItem);
                //}
                Invalidate();

                //if (SelectedItemChanged != null)
                //{
                //    SelectedItemChanged(this, new EventArgs());
                //}
            }
        }

        /// <summary>
        /// Cleans up the background paint buffer.
        /// </summary>
        private void CleanupBackBuffer()
        {
            if (m_backBufferBitmap != null)
            {
                m_backBufferBitmap.Dispose();
                m_backBufferBitmap = null;
                m_backBuffer.Dispose();
                m_backBuffer = null;
            }
        }

        /// <summary>
        /// Creates the background paint buffer.
        /// </summary>
        private void CreateBackBuffer()
        {
            CleanupBackBuffer();

            m_backBufferBitmap = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);
            m_backBuffer = Graphics.FromImage(m_backBufferBitmap);
        }

        /// <summary>
        /// Clips the scroll position.
        /// </summary>
        private void ClipScrollPosition()
        {
            if (m_offset.X < 0)
            {
                m_offset.X = 0;
                m_velocity.X = 0;
            }
            else if (m_offset.X > MaxXOffset)
            {
                m_offset.X = MaxXOffset;
                m_velocity.X = 0;
            }
            if (m_offset.Y < 0)
            {
                m_offset.Y = 0;
                m_velocity.Y = 0;
            }
            else if (m_offset.Y > MaxYOffset)
            {
                m_offset.Y = MaxYOffset;
                m_velocity.Y = 0;
            }
        }

        /// <summary>
        /// Clips the velocity.
        /// </summary>
        private void ClipVelocity()
        {
            m_velocity.X = Math.Min(m_velocity.X, m_maxVelocity);
            m_velocity.X = Math.Max(m_velocity.X, -m_maxVelocity);

            m_velocity.Y = Math.Min(m_velocity.Y, m_maxVelocity);
            m_velocity.Y = Math.Max(m_velocity.Y, -m_maxVelocity);
        }

        /// <summary>
        /// Finds the bounds for the specified item.
        /// </summary>
        /// <param name="x">The item x index.</param>
        /// <param name="y">The item y index.</param>
        /// <returns>The item bounds.</returns>
        private Rectangle ItemBounds(int x, int y)
        {
            int itemX = Bounds.Left + (m_itemWidth * x);
            int itemY = m_itemHeight * y;

            if (m_layout == KListLayout.Vertical)
            {
                return new Rectangle(ClientRectangle.Left, itemY, ItemWidth, ItemHeight);
            }
            else if (m_layout == KListLayout.Horizontal)
            {
                return new Rectangle(itemX, ClientRectangle.Top, ItemWidth, ItemHeight);
            }
            else
            {
                return new Rectangle(itemX, itemY, ItemWidth, ItemHeight);
            }
        }

        /// <summary>
        /// Finds the index for the specified y offset.
        /// </summary>
        /// <param name="x">The x offset.</param>
        /// <param name="y">The y offset.</param>
        /// <returns></returns>
        private Point FindIndex(int x, int y)
        {
            Point index = new Point(0, 0);

            if (m_layout == KListLayout.Vertical)
            {
                index.Y = ((y + m_offset.Y - ClientRectangle.Top) / (m_itemHeight));
            }
            else if (m_layout == KListLayout.Horizontal)
            {
                index.X = ((x + m_offset.X - ClientRectangle.Left) / (m_itemWidth));
            }
            else
            {
                index.X = ((x + m_offset.X - ClientRectangle.Left) / (m_itemWidth));
                index.Y = ((y + m_offset.Y - ClientRectangle.Top) / (m_itemHeight));
            }

            if (index.Y < 0)
                index.Y = 0;

            return index;
        }

        /// <summary>
        /// Gets the maximum x offset.
        /// </summary>
        /// <value>The maximum x offset.</value>
        private int MaxXOffset
        {
            get
            {
                return Math.Max(((m_items.Count * ItemWidth)) - ClientRectangle.Width, 0);
            }
        }

        /// <summary>
        /// Gets the maximum y offset.
        /// </summary>
        /// <value>The maximum y offset.</value>
        private int MaxYOffset
        {
            get
            {
                if (m_items.Count > 0)
                    return Math.Max(((m_items[0].Count * ItemHeight)) - ClientRectangle.Height, 0);
                else
                    return 0;
            }
        }

        // The items!
        class ItemList : Dictionary<int, IKListItem>
        {
        }
        class GridList : Dictionary<int, ItemList>
        {
        }
        GridList m_items = new GridList();

        /// <summary>
        /// Layout Mode.
        /// </summary>
        public enum KListLayout
        {
            /// <summary>
            /// Vertically scrolling list.
            /// </summary>
            Vertical,

            /// <summary>
            /// Horizontally scrolling list.
            /// </summary>
            Horizontal,

            /// <summary>
            /// Scrolling grid.
            /// </summary>
            Grid
        };

        // Properties
        int m_maxVelocity = 15;
        int m_itemHeight = 40;
        int m_itemWidth = 80;
        KListLayout m_layout = KListLayout.Vertical;
        bool m_updating = false;
        SkinSettings skinSettings;
        bool vga = false;
        bool wideFastAccess = false;

        // Background drawing
        Bitmap m_backBufferBitmap;
        Graphics m_backBuffer;

        // Motion variables
        Point m_selectedIndex = new Point(-1, -1);
        IKListItem m_selectedItem = null;
        Point m_velocity = new Point(0, 0);
        Point m_mouseDown = new Point(-1, -1);
        Point m_mousePrev = new Point(-1, -1);
        Timer m_timer = new Timer();
        int m_timerCount = 0;
        Point m_offset = new Point();

        Point startIndex = new Point();
        bool moreX = false;
        bool moreY = false;
        ItemList yList = new ItemList();
        IKListItem currentItem;
        Rectangle itemRect;
        Rectangle rectangleImageBG;
        SizeF sizeFont;
        SolidBrush drawStringBrush;

        private StringFormat m_stringFormat = new StringFormat();

        int m_fastAccessWidth = 20;
        Point m_mouseFastAccess = new Point(-1, -1);
        Rectangle fastAccessRect = new Rectangle();
        int fastAccessItem = 0;
        string fastAccess = "";

        bool isFocused = false;
        
        SortedList<string, Bitmap> trainIcons;

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // KListControl
            // 
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
            this.Name = "KListControl";
            this.ResumeLayout(false);

        }

        protected override void OnDoubleClick(EventArgs e)
        {
            base.OnDoubleClick(e);

            IKListItem lastItem = m_items[0][m_items[0].Count - 1];
            Rectangle lastItemRect = new Rectangle(lastItem.Bounds.X, lastItem.Bounds.Y, this.ItemWidth, lastItem.Bounds.Height);

            lastItemRect.Offset(-m_offset.X, -m_offset.Y);

            if (m_mousePrev.Y > (lastItemRect.Y + lastItemRect.Height))
            {
                //ignore double click
            }
            else
            {
                if (OnItemDoubleClicked != null)
                {
                    OnItemDoubleClicked(this.Parent, e);
                }
            }
        }
        
    }
}
